/*
 *  Trial.cpp
 *  gre
 *
 *  Created by Kutlak Roman on 04/04/2012.
 *  Copyright 2012 Kutlak Roman. All rights reserved.
 *
 *
 *  The code is based on Bartosz Milewski's code 
 *  for the C++11 concurrency tutorial.
 *  http://www.corensic.com/Learn/Resources/ConcurrencyTutorialPartOne.aspx
 */

#pragma once

#include <sys/stat.h>
#include <dirent.h>
#include <cassert>

#include <string>

#define SEPARATOR '/'

#pragma mark -
#pragma mark class path

class path
{
public:
	path()
    {}
	
    path(std::string const & str) : _str(str)
    {}

	const std::string file_string() const
    { return _str; }
	
    std::string leaf() const 
	{ 
		size_t i = _str.find_last_of(SEPARATOR);
		if (i == std::string::npos)
			return std::string();
		return _str.substr(i + 1, _str.length() - i);
	};
	
    path & remove_leaf()
	{
		size_t i = _str.find_last_of(SEPARATOR);
		if (i != std::string::npos)
			_str = _str.substr(0, i);
		return *this;
	}
	
    path & operator/=(std::string const & segment)
	{
		_str += SEPARATOR;
		_str += segment;
		return *this;
	}
    
    path & operator/=(std::string&& segment)
	{
		_str += SEPARATOR;
		_str += std::move(segment);
		return *this;
	}
    
    bool empty() const
    { return !_str.empty(); }
    
private:
	std::string _str;
};


#pragma mark -
#pragma mark class basic_directory_entry

template<class Path>
class basic_directory_entry
{
public:
	friend class directory_iterator;
	
    basic_directory_entry() {}
	
    basic_directory_entry(Path const & pth) : _path(pth) {}
	
    Path const & path() const { return _path; }

	bool is_directory() const { return (S_ISDIR(_data.st_mode)); }
    
private:
	bool is_dots () const 
	{
        std::string filename = _path.leaf();
		return (filename [0] == '.' && 
                (filename [1] == '\0' ||  // single dot
				(filename [1] == '.' &&
                 filename [2] == '\0'))); // double dot
	}
    
	void update(const std::string& newPath)
	{
        update(std::string(newPath));
	}
    
    void update(std::string&& newPath)
	{
        _path.remove_leaf();
		_path /= std::move(newPath);

        int res = stat(_path.file_string().c_str(), &_data);
        switch (res)
        {
            case 0: ; // all fine
                break;
                
            case ENOENT:
                throw (std::runtime_error
                       ("A component of path does not exist."));
                break;
            case ENOTDIR:
                throw (std::runtime_error
                       ("A component of the path is not a directory."));
                break;                
            case ELOOP:
                throw (std::runtime_error
                       ("Too many symbolic links encountered."));
                break;
            case EACCES:
                throw (std::runtime_error("Permission denied."));
                break;
            case ENAMETOOLONG:
                throw (std::runtime_error("Path too long."));
                break;
            default:
                throw (std::runtime_error
                       ("Exception while reading stats for " +
                        _path.file_string()));
        }
	}
    
	struct stat _data;
	Path _path;
};


#pragma mark -
#pragma mark class directory_iterator

class directory_iterator: public std::iterator
<std::input_iterator_tag, basic_directory_entry<path>>
{
public:
	typedef basic_directory_entry<path> directory_entry;

	directory_iterator() : _atEnd(true), _handle(0) {}
    
	directory_iterator(path const & pth) : _dir_entry(pth)
	{
        
        errno = 0;
		_handle = opendir(_dir_entry._path.file_string().c_str());

		if (NULL == _handle)
		{
			if (errno == ENOTDIR 
                || errno == ENOENT 
                || errno == EACCES 
                || errno == ELOOP 
                || errno == ENAMETOOLONG)
			{
				_atEnd = true;
				errno = 0;
			}
			else
			{
                switch (errno)
                {
                    case 0: ; // all fine
                        break;
                        
                    case ENOENT:
                        throw (std::runtime_error
                               ("A component of path does not exist."));
                        break;
                    case ENOTDIR:
                        throw (std::runtime_error
                               ("A component of the path is not a directory."));
                        break;                
                    case ELOOP:
                        throw (std::runtime_error
                               ("Too many symbolic links encountered."));
                        break;
                    case EACCES:
                        throw (std::runtime_error("Permission denied."));
                        break;
                    case ENAMETOOLONG:
                        throw (std::runtime_error("Path too long."));
                        break;
                    default:
                        throw (std::runtime_error
                               ("Exception while opening " +
                                _dir_entry._path.file_string()));
                }
			}
		}
		else
        {
            _dir_entry._path /= "*";
            stat(_dir_entry._path.file_string().c_str(), &_dir_entry._data);
			_atEnd = false;
        }
		// skip "." and ".." directories
        do {
            this->operator++();
        } while (!_atEnd && _dir_entry.is_directory() && _dir_entry.is_dots());
	}
    
	directory_iterator(directory_iterator const & it)
	{
		assert(!"copy constructor");
	}
    
	directory_iterator& operator=(directory_iterator const & it)
	{
		assert(!"copy assignment");
		return *this;
	}
    
	~directory_iterator()
	{
		if (NULL != _handle)
        {
			closedir(_handle);
            _handle = NULL;
        }
	}
    
	void operator++()
	{
        struct dirent *dp;
        int res = readdir_r(_handle, &_begin, &dp);

        if (NULL == dp && 0 == res)
        {
            _atEnd = true;
        }
        else
        {
            switch (res)
            {
                case 0: ; // all fine
                    break;
                    
                case ENOENT:
                    throw (std::runtime_error
                           ("A component of path does not exist."));
                    break;
                case ENOTDIR:
                    throw (std::runtime_error
                           ("A component of the path is not a directory."));
                    break;                
                case ELOOP:
                    throw (std::runtime_error
                           ("Too many symbolic links encountered."));
                    break;
                case EACCES:
                    throw (std::runtime_error("Permission denied."));
                    break;
                case ENAMETOOLONG:
                    throw (std::runtime_error("Path too long."));
                    break;
                default:
                    throw (std::runtime_error
                           ("Exception while reading stats for " +
                            _dir_entry._path.file_string()));
            }
            
            _dir_entry.update(dp->d_name);
        }
	}
    
	bool operator==(directory_iterator const & it) const
	{
		return it._atEnd && _atEnd;
	}
    
	bool operator!=(directory_iterator const & it) const
    { return !(*this == it); }
    
	directory_entry const * operator->() const
    { return &_dir_entry; }

private:
    bool            _atEnd;
    DIR*            _handle;
    struct dirent   _begin;
	directory_entry _dir_entry;
};


#pragma mark -
#pragma mark helper functions
inline 
bool is_directory(const std::string& path)
{
    struct stat data;
    stat(path.c_str(), &data);
    return (S_ISDIR(data.st_mode));
}

inline 
bool is_directory(const path& p)
{
    struct stat data;
    stat(p.file_string().c_str(), &data);
    return (S_ISDIR(data.st_mode));
}

inline 
bool exists(const std::string& path)
{
    struct stat data;
    int res = stat(path.c_str(), &data);
    return (!res);
}

inline 
bool exists(const path& p)
{
    struct stat data;
    int res = stat(p.file_string().c_str(), &data);
    return (!res);
}

inline 
bool is_regular(const std::string& path)
{
    struct stat data;
    stat(path.c_str(), &data);
    return (S_ISREG(data.st_mode));
}

inline 
bool is_regular(const path& p)
{
    struct stat data;
    stat(p.file_string().c_str(), &data);
    return (S_ISREG(data.st_mode));
}

inline 
long long file_size(const std::string& path)
{
    struct stat data;
    stat(path.c_str(), &data);
    return (data.st_size);
}

inline 
long long file_size(const path& p)
{
    struct stat data;
    stat(p.file_string().c_str(), &data);
    return (data.st_size);
}

// Example:
//  path pth("c:\\bin");
//  for (directory_iterator it(pth); it != directory_iterator(); ++it)
//  {
//  	std::cout << it->path().file_string() << " [" << it->path().leaf() << "]\n";
//  }
